#region Microsoft Public License
/*
 * (c) Copyright Chenjun Wu. http://utablesdk.codeplex.com
 * 
 * This source is subject to the Microsoft Public License (Ms-PL).
 * Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details.
 * All other rights reserved.
 */
#endregion

﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;

namespace UTable.Objects.Policies
{
    public enum PhysicalContactStateType
    {
        Hold,
        Move,
    }

    public abstract class PhysicalContactState
    {
        private PhysicalContact _contact;
        private Point _initialPosition;


        public PhysicalContactState(PhysicalContact contact, Point initialPosition)
        {
            _contact = contact;
            _initialPosition = initialPosition;
        }

        /// <summary>
        /// The type of the contact state
        /// </summary>
        public abstract PhysicalContactStateType Type { get;}

        /// <summary>
        /// The contact that holds this state
        /// </summary>
        public PhysicalContact Contact { get { return _contact; } }

        /// <summary>
        /// The initial position of this state
        /// That is the position of the contact when this state is created
        /// </summary>
        public Point InitialPosition { get { return _initialPosition; } }

        /// <summary>
        /// Called when the contact's position is updated
        /// </summary>
        /// <param name="position">the new position of the contact</param>
        /// <param name="nextState">the next state that the state machine should move to. null if not move</param>
        /// <param name="positionEffective">figure whether the position should be updated</param>
        public abstract void PositionUpdated(Point position, out PhysicalContactState nextState, out bool positionEffective);
    }

    /// <summary>
    /// 
    /// </summary>
    public class PhysicalContactHoldState: PhysicalContactState
    {
        public PhysicalContactHoldState(PhysicalContact contact, Point initialPosition)
            : base(contact, initialPosition)
        {
            HoldPosition = InitialPosition;
            //TODO: Set the value properly
            MaxHoldDistance = 10;
        }

        public override PhysicalContactStateType Type { get { return PhysicalContactStateType.Hold; } }

        /// <summary>
        /// The position where this contact is holding
        /// </summary>
        public Point HoldPosition { get; set; }

        /// <summary>
        /// The maximum distance that the contact can stray from the hold position
        /// </summary>
        public double MaxHoldDistance { get; set; }

        public override void PositionUpdated(Point position, out PhysicalContactState nextState, out bool positionEffective)
        {
            if ((position - HoldPosition).Length > MaxHoldDistance)
            {
                // go to Move state
                nextState = new PhysicalContactMoveState(this.Contact, position);
                positionEffective = true;
            }
            else
            {
                // still hold
                nextState = null;
                positionEffective = false;
            }
        }
    }

    /// <summary>
    /// 
    /// </summary>
    public class PhysicalContactMoveState : PhysicalContactState
    {
        private DateTime _holdTime;

        /// <summary>
        /// The position where this contact is holding
        /// </summary>
        public Point HoldPosition { get; set; }

        /// <summary>
        /// The maximum distance that the contact can stray from the hold position
        /// </summary>
        public double MaxHoldDistance { get; set; }

        /// <summary>
        /// The current hold energy 
        /// </summary>
        public double HoldEnergy { get; set; }

        /// <summary>
        /// The maximum hold energy, measured in milliseconds
        /// When the <see cref="UTable.Objects.Policies.PhysicalContactMoveState.HoldEnergy"/> is 
        /// larger than <see cref="UTable.Objects.Policies.PhysicalContactMoveState.MaxHoldEnergy"/>, 
        /// the state will change to <see cref="UTable.Objects.Policies.PhysicalContactHoldState"/>
        /// </summary>
        public double MaxHoldEnergy { get; set; }

        public PhysicalContactMoveState(PhysicalContact contact, Point initialPosition)
            : base(contact, initialPosition)
        {
            HoldPosition = InitialPosition;
            HoldEnergy = 0;
            _holdTime = DateTime.Now;

            //TODO: Set the value properly
            MaxHoldDistance = 10;
            //TODO: Set the value properly
            MaxHoldEnergy = 999;
        }

        public override PhysicalContactStateType Type { get { return PhysicalContactStateType.Move; } }

        public override void PositionUpdated(Point position, out PhysicalContactState nextState, out bool positionEffective)
        {
            if ((position - HoldPosition).Length > MaxHoldDistance)
            {
                HoldPosition = position;
                HoldEnergy = 0;
                _holdTime = DateTime.Now;
            }
            else
            {
                HoldEnergy = (DateTime.Now - _holdTime).TotalMilliseconds;
            }

            if (HoldEnergy > MaxHoldEnergy)
            {
                // go to Hold state
                nextState = new PhysicalContactHoldState(this.Contact, position);
                positionEffective = false;
            }
            else
            {
                nextState = null;
                positionEffective = true;
            }
        }
    }

    /// <summary>
    /// A user contact in the physical engine. Created by a finger down event
    /// Implemented as State Machine
    /// </summary>
    public class PhysicalContact
    {
        public PhysicalContactState State { get; set; }

        /// <summary>
        /// The current position of the contact
        /// </summary>
        public Point Position { get; set; }

        /// <summary>
        /// The previous position of the contact
        /// </summary>
        public Point PreviousPosition { get; set; }

        /// <summary>
        /// The initial position of the contact. 
        /// That is the position of the finger-down event
        /// </summary>
        public Point InitialPosition { get; set; }

        /// <summary>
        /// The ID of the finger that causes this contact
        /// </summary>
        public int FingerID { get; set; }

        public PhysicalContact(int id, Point position)
        {
            this.Position = position;
            this.PreviousPosition = position;
            this.InitialPosition = Position;
            this.FingerID = id;

            // The initial state is hold
            this.State = new PhysicalContactHoldState(this, InitialPosition);
        }

        /// <summary>
        /// Update the position of the contact
        /// This function will be called when the contact is moved
        /// </summary>
        /// <param name="position">the new position of the contact</param>
        /// <returns>true if the contact is really moved, false is the movement is caoused by noise</returns>
        public bool UpdatePosition(Point position)
        {
            this.PreviousPosition = this.Position;
            this.Position = position;

            bool positionEffective = true;
            PhysicalContactState nextState = null;
            this.State.PositionUpdated(position, out nextState, out positionEffective);
            if (nextState != null)
                this.State = nextState;

            return positionEffective;
        }
    }

}
